package cinvestment;

import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloModel;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import support.AlgorithmSupport;
import util.Lang;
import util.MathUtil;
import util.NullOutputStream;

public class Example {
	private int N = 2;
	private int n = 3;
	private double[][] demands;
	private List<Double[][]> pi;
	private List<Double[][]> sigma;

	private double epsilon = 0.001;

	private int iter = 1;

	private int sn = 50;

	private double probability;

	public void lShapedExample() {
		this.demands = AlgorithmSupport.getDemands_uniform(sn, N, 0, 2);
		this.probability = BigDecimal.valueOf(1)
				.divide(BigDecimal.valueOf(sn), 4, BigDecimal.ROUND_HALF_EVEN)
				.doubleValue();
		this.pi = new ArrayList<>();
		this.sigma = new ArrayList<>();

		List<Double[]> outputData = new ArrayList<>();

		while (true) {
			MasterResult mr = null;
			if (iter == 1) {
				int[] x = { 0, 0, 1 };
				double[] k = { 0.0, 0.0, 1.4 };
				mr = new MasterResult();
				mr.setValue(0.0, x, k, Double.NEGATIVE_INFINITY);
			} else {
				mr = this.solveMaster();
			}

			for (int m = 0; m < sn; m++) {
				this.solveDual(m, mr.k);
			}

			double w = 0.0;
			for (int m = 0; m < sn; m++) {
				for (int i = 0; i < N; i++) {
					w += this.demands[m][i] * this.pi.get(iter - 1)[m][i];
				}
				for (int j = 0; j < n; j++)
					w += this.sigma.get(iter - 1)[m][j] * mr.k[j];
			}
			w = w * this.probability;

			Double[] resultIter = new Double[3];
			resultIter[0] = mr.objValue;
			resultIter[1] = mr.theta;
			resultIter[2] = w;
			outputData.add(resultIter);

			// if (w - mr.theta < this.epsilon) {
			if (w <= mr.theta) {
				System.out.println("======optimal=====");
				System.out.println("gap="
						+ MathUtil.DOUBLE_FORMAT_4.format(w - mr.theta));
				Lang.print(mr.x);
				Lang.print(mr.k, MathUtil.DOUBLE_FORMAT_2);

				AlgorithmSupport.storeResult(outputData, sn, N);

				break;
			} else {
				System.out.println("======iter " + iter + "=====");
				System.out.println("gap="
						+ MathUtil.DOUBLE_FORMAT_4.format(w - mr.theta));
				Lang.print(mr.x);
				Lang.print(mr.k, MathUtil.DOUBLE_FORMAT_2);

				iter++;
			}
		}
	}

	private MasterResult solveMaster() {
		try {
			IloCplex cplex = new IloCplex();
			cplex.setOut(new NullOutputStream());
			IloNumVar[] K = cplex.numVarArray(n, 0.0, Double.MAX_VALUE);
			IloNumVar[] X = cplex.boolVarArray(n);

			IloLinearNumExpr obj = cplex.linearNumExpr();

			double[] xc = { 0.5, 0.5, 1.0 };
			double[] kc = { 0.25, 0.25, 0.3125 };
			obj.addTerms(xc, X);
			obj.addTerms(kc, K);

			IloNumVar theta = cplex.numVar(Double.NEGATIVE_INFINITY,
					Double.MAX_VALUE);

			// if there are generated cuts
			if (this.pi != null && this.pi.size() > 0) {
				obj.addTerm(1.0, theta);

				for (int iter = 0; iter < pi.size(); iter++) {
					IloLinearNumExpr r1 = cplex.linearNumExpr();
					double r2 = 0.0;
					for (int m = 0; m < sn; m++) {
						for (int j = 0; j < n; j++)
							r1.addTerm(probability * sigma.get(iter)[m][j],
									K[j]);
						for (int i = 0; i < N; i++) {
							r2 += probability * pi.get(iter)[m][i]
									* this.demands[m][i];
						}
					}
					cplex.addGe(theta, cplex.sum(r1, r2));
				}
			}

			cplex.addMinimize(obj);

			for (int j = 0; j < n; j++)
				cplex.addGe(cplex.prod(Double.MAX_VALUE, X[j]), K[j]);

			// cplex.exportModel(Lang.projectRoot() + "\\data\\master\\master"
			// + iter + ".lp");

			MasterResult mr = new MasterResult();
			if (cplex.solve()) {
				int[] Xv = new int[n];
				for (int j = 0; j < n; j++)
					Xv[j] = (int) cplex.getValue(X[j]);
				double thetaValue = Double.NEGATIVE_INFINITY;

				if (this.pi != null && this.pi.size() > 0) {
					thetaValue = cplex.getValue(theta);
				}
				mr.setValue(cplex.getObjValue(), Xv, cplex.getValues(K),
						thetaValue);
			}
			cplex.end();
			return mr;

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private void solveDual(int m, double[] k) {
		try {
			IloCplex cplex = new IloCplex();
			// turn off the log output to console
			cplex.setOut(new NullOutputStream());
			IloNumVar[] pi_i = cplex.numVarArray(N, Double.NEGATIVE_INFINITY,
					0.0);
			IloNumVar[] sigma_i = cplex.numVarArray(n,
					Double.NEGATIVE_INFINITY, 0.0);

			IloLinearNumExpr obj = cplex.linearNumExpr();
			for (int i = 0; i < N; i++) {
				obj.addTerm(this.demands[m][i], pi_i[i]);
			}
			for (int j = 0; j < n; j++)
				obj.addTerm(k[j], sigma_i[j]);
			cplex.addMaximize(obj);

			cplex.addLe(cplex.sum(pi_i[0], sigma_i[0]), -1.0);
			cplex.addLe(cplex.sum(pi_i[0], sigma_i[2]), -1.0);
			cplex.addLe(cplex.sum(pi_i[1], sigma_i[1]), -1.0);
			cplex.addLe(cplex.sum(pi_i[1], sigma_i[2]), -1.0);

			// cplex.addLe(cplex.sum(pi_i[0], sigma_i[1]), -1.0);
			// cplex.addLe(cplex.sum(pi_i[1], sigma_i[0]), -1.0);

			// cplex.exportModel(Lang.projectRoot() + "\\data\\dual\\dual" +
			// iter
			// + "_" + m + ".lp");

			if (cplex.solve()) {
				if (this.pi.size() < iter) {
					this.pi.add(new Double[sn][N]);
					this.sigma.add(new Double[sn][n]);
				}
				for (int i = 0; i < N; i++)
					this.pi.get(iter - 1)[m][i] = cplex.getValue(pi_i[i]);
				for (int j = 0; j < n; j++)
					this.sigma.get(iter - 1)[m][j] = cplex.getValue(sigma_i[j]);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private class MasterResult {
		double objValue;
		int[] x;
		double[] k;
		double theta;

		void setValue(double objValue, int[] x, double[] k, double theta) {
			this.objValue = objValue;
			this.x = Lang.clone(x);
			this.k = Lang.clone(k);
			this.theta = theta;
		}
	}
}
